/*
 * project1.c
 *
 *  Created on: Sep 05, 2011
 *      Author: Michael Dean
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <time.h>

//############################### PROTOTYPES ###################################
void displayPrompt();				//display user input prompt
void findBuiltIns();				//check if the user input is a built in cmd
void parseCommand();				//tokenize the user input 
void readCommandLine();				//read in the user input
int verifyExistence(char *, char *, char **);	//verify if function path is valid
int findPath(char *, char **);			//find the command path
void changeDirectory();
void echoPrint();
void whichCommand();
int viewProcCmd();
void showHistory();
void backgroundExec();
void outputDirec(int);				//redirect output
void inputDirec(int);				//redirect input
void pipingCall();					//activate a piping call
int checkRedirection(int);			//check for bad i/o
void setPipes(char **, int);		//set piping variables
char * findTime();					//check if command is running and time

void addToHistory();		
void printContent();
void increaseHistory();

//**********************************GLOBAL VARIABLES*******************************
int sizeConstant = 80;			//global size value
char *cmdStr,               	//full command path
		*icmd,             //input command removed from full input
      input[80];                //user typed command line
char * arguments[80],			//array to hold arguments
		*arguments2[80],		//for piping
		*arguments3[80],		//for piping
		*arguments4[80],		//for piping
		*historyCmd[10],
		*historyStart[10],
		*historyEnd[10],
		*historyStatus[10],
		*startTime,
		*endTime;
int bkgndOn = 0,				//set to 0 for 0, 1 for WNOHANG
	outDirec = 0,				//set to zero if no output redirecting
	inDirec = 0,				//set to zero if no input redirecting
	pipeActive = 0,				//set to zero if no piping call
	Location = 0,
	pipe1 = 0,
	pipe2 = 0,
	pipe3 = 0;
	historySize = 0;
char pwd[128];



//############################ FUNCTION DEFINITIONS ########################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void displayPrompt(){
  char hostname[128];

    hostname[127] = '\0'; 
    gethostname(hostname, 128);
	
    //printf("machine name, e.g., hostname: %s\n", hostname);
  printf("%s@%s:%s::-> ", getenv("USER"), hostname, pwd);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void addToHistory(){
	int i = historySize;
	char * fullInput;
	
	fullInput = strdup(input);
	
	historyCmd[i] = fullInput;
	
	historyStart[historySize] = startTime;
	historyEnd[historySize] = endTime;
	
	if(viewProcCmd == 1)
		historyStatus[historySize] = "running";

		//save process id for when it finishes
	else
		historyStatus[historySize] = "exited";
	
	historySize++;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
char * findTime(){
	
	char * timestring[9];		//set max space for HH:MM:SS
	time_t secs = time(0);

	struct tm *local = localtime(&secs);	//convert to localtime
	
	sprintf(timestring, "%02d:%02d:%02d", local->tm_hour, local->tm_min, local->tm_sec);
	
	char * t = strdup(timestring);
	
	return t;
	
//	timeVal = strdup(timestring);
	
	//set string using time functions
	
	//~ historyStart[historySize] = strdup(timestring);
	//~ historyEnd[historySize] = strdup(timestring);
	
	
	
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void printContent(){
	int i = 0;

//	puts("Displaying History");

	if (historySize == 0)
	{
		puts("History is Empty");
	}
	
	while(i != historySize)
	{
		printf("%s %s %s %s\n", historyStart[i], historyEnd[i], historyCmd[i],
				historyStatus[i]);
		i++;
	}
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void findBuiltIns(){  //check if command is echo, cd, history
	strcpy(arguments[0], icmd);

	if (!strcmp(icmd, "cd"))
		changeDirectory();
	
	else if (!strcmp(icmd, "echo"))
		echoPrint();
	
	else if (!strcmp(icmd, "history"))
		showHistory();
	
	else if (!strcmp(icmd, "viewproc"))
		viewProcCmd();
	
	else if (!strcmp(icmd, "which"))
		whichCommand();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void changeDirectory(){
	
	//puts("Changing Directory\n");
	if (arguments[1] !=	NULL) {    //cant check a null pointer
		if(!chdir(arguments[1]));		//did it change directories? (exist)
			//puts("Path found\n");		//ERROR CHECKING
		else
			puts("Path not found");		//ERROR CHECKING
	}
	getcwd(pwd,128);

}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void echoPrint(){
	//char *temp;
	char * dollar;
	int count = 1;//, tempcount = 0;
//	puts("Echo Print\n");  				//ERROR CHECKING
	
//path, host, pwd
	while(arguments[count] != NULL)
	{
		if (strchr(arguments[count], '$'))
		{
			dollar = strtok(arguments[count], "$");			//set to search for $
			if (dollar != NULL)
			{
				if(getenv(dollar))
					printf("%s", getenv(dollar));
				else
					printf("Invalid variable: %s", dollar);
			}
		}
		else
			printf("%s", arguments[count]);
		
		printf(" ");
		count++;
	}		//end while
	puts("");
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void showHistory(){
	
	//puts("Showing History\n");		//ERROR CHECKING
	printContent();
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void whichCommand(){
	
//	puts("Finding path for command");		//ERROR CHECKING
	findPath(arguments[1], arguments);			//locate the path of the users argument
	if (!strcmp(arguments[1], "cd") || !strcmp(arguments[1], "echo") 
		|| !strcmp(arguments[1], "history")	|| !strcmp(arguments[1], "viewproc") 
		|| !strcmp(arguments[1], "which") || !strcmp(arguments[1], "exit"))
		printf("%s: Shell built-in command\n", arguments[1]);
	else if (arguments[0] != NULL)
		printf("%s\n", cmdStr);
	else 
		puts("Invalid");
	
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int viewProcCmd(){
	//using fgetc from C++ website
	FILE * pFile;
	char * procLoc = "/proc";
	int c;

	if(verifyExistence(procLoc, arguments[1], arguments))
	{
//		printf("Exists\n");			//ERROR CHECKING
		pFile = fopen (arguments[0],"r");
		if (pFile == NULL) 
			perror ("Error opening file");
		else
		{
			do 
			{
				c = fgetc (pFile);
				if (c != EOF)
					printf("%c", c);
			} while (c != EOF);
				fclose (pFile);
			return 1;
		}
	}
	else
		printf("error: /proc/%s is not in /proc.\n", arguments[1]);
	return 0;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int checkRedirection(int location){			//check if redirection is correct
	//check if no file is specified
	if ((outDirec > 0) || (inDirec > 0)) {
		if (arguments[location + 1] == NULL)
		{
			puts("Invalid filename");
			return 0;
		}
		//check if no command is given for redirection
		else if (arguments[location -1] == NULL)
		{
			puts("Invalid command");
			return 0;
		}
	}
	return 1;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void outputDirec(int location){			//redirect output
	//uses last argument spot (filename) to open
	char * fileName = arguments[location + 1];		//used to store user output file
	//puts("Opening file for output redirection");		//ERROR CHECKING
	int fd;
	
//	printf("Filename = %s\n", fileName);		//ERROR CHECKING
	fd=open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0600);	//create with rw permissions
	//close(1);
	dup2(fd,1);		//1 is for std output
	close(fd);
	//readjust the arguments array to not include the '>' anymore as a flag	
	arguments[location + 1] = '\0';
	arguments[location] = '\0';

}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void inputDirec(int location){				//redirect input
//	puts("Opening file for input redirection");		//ERROR CHECKING
	char * fileName = arguments[location + 1];		//used to store user output file
	int fd;
	
	if ((fd=open(fileName, O_RDWR)) == -1) {
        fprintf(stderr, "file error: can't open %s\n", fileName);
        exit(1);
        }
    dup2(fd, 0);		//0 is for std input
    close(fd);
    arguments[location + 1] = '\0';
	arguments[location] = '\0';

}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void pipingCall(){				//activate a piping call
	//puts("Begin piping");		//ERROR CHECKING
		
	//printf("Pipe 3 = %i\n", pipe3);		//ERROR CHECKING
	if (pipe3 > 0)
	{
		setPipes(arguments4, pipe3);
		if (findPath(icmd, arguments4))
			arguments4[0] = cmdStr;
	}
	//printf("Pipe 2 = %i\n", pipe2);		//ERROR CHECKING
	if (pipe2 > 0)
	{
		setPipes(arguments3, pipe2);
		if (findPath(icmd, arguments3))
			arguments3[0] = cmdStr;
	}
	//printf("Pipe 1 = %i\n", pipe1);		//ERROR CHECKING
	if (pipe1 > 0)
	{
		setPipes(arguments2, pipe1);
		if (findPath(icmd, arguments2))
			arguments2[0] = cmdStr;
	}
	//printf("PipeActive = %i\n", pipeActive);	//ERROR CHECKING
	//ERROR CHECKING
	/*
	int i;
	for (i = 0; arguments4[i] != NULL; i++)
		printf("Arguments4[%i] = %s\n", i, arguments4[i]);
	for (i = 0; arguments3[i] != NULL; i++)
		printf("Arguments3[%i] = %s\n", i, arguments3[i]);
	for (i = 0; arguments2[i] != NULL; i++)
		printf("Arguments2[%i] = %s\n", i, arguments2[i]);
	for (i = 0; arguments[i] != NULL; i++)
		printf("Arguments[%i] = %s\n", i, arguments[i]);
	puts("Printing Done\n");
	*/
	//ERROR CHECKING
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void setPipes(char ** args, int pipeLoc)		//set piping strings
{
	int count = 0;
	int i = 0;
	arguments[pipeLoc] = '\0';
	icmd = arguments[pipeLoc + 1];
	for(count = pipeLoc + 1; arguments[count] != NULL; count++, i++)
	{
//		printf("ArgumentsCount[%i] = %s\n", count, arguments[count]);	//ERROR CHECKING
		args[i] = arguments[count];		//set up 3rd pipe string
		arguments[count] = '\0';				//clear out from main string
//		printf("Argsi[%i] = %s\n", i, args[i]);		//ERROR CHECKING
	}
	
	args[i] = '\0';			//set final array spot to null
	
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void linkPipes(){
	const int read = 0,
				write = 1;
	int p1_to_p2[2],
		p2_to_p3[2],
		p3_to_p4[2];				//spot 0 is for reading, spot 1 for writing
	pid_t pid, pid2, pid3, pid4;	//stores process ID
	int status;
	
	//set up pipe arguments
	pipingCall();
	
						//~~~~~~~~~PIPE ONE~~~~~~~~~~~~~//
	//set piping arrays
	pipe(p1_to_p2);		//1 is for writing, 0 is for reading
	
	pid = fork();
	
	if(pid == 0)
	{
		close(write);				//close std-in
		dup(p1_to_p2[write]);		//copy content of std-input into p1_to_p2[0] (reading)
		close(p1_to_p2[write]);		//close the writing part of our created pipe
		close(p1_to_p2[read]);		//close the reading part of the pipe
		
		execv(arguments[0], arguments);
	}

	if (pipe2 > 0)	
		pipe(p2_to_p3);		//create new pipe to be forked
	
	pid2 = fork();		//fork with newly created pipe

	if(pid2 == 0)
	{
		close(read);			//closes stdout so you don't see arguments on screen
		dup(p1_to_p2[read]);	//duplicate output of arguments into p1_to_p2[1] (writing)
		close(p1_to_p2[read]);	
		close(p1_to_p2[write]);
		
	//start working with second pipe
		if (pipe2 > 0)
		{
			close(write);
			dup(p2_to_p3[write]);
			close(p2_to_p3[read]);
			close(p2_to_p3[write]);
			
			execv(arguments2[0], arguments2);
		}
		else
			execv(arguments2[0], arguments2);
	}
	if (pipe2 > 0)
	{
		//pipe(p3_to_p4)
		
		pid3 = fork();
		
		if(pid3 == 0)
		{
			close(read);
			dup(p2_to_p3[read]);
			close(p2_to_p3[read]);
			close(p2_to_p3[write]);

			//~ close(write);
			//~ dup(p3_to_p4[write]);
			//~ close(p3_to_p4[read]);
			//~ close(p3_to_p4[write]);
			
			execv(arguments3[0], arguments3);
		}	
	}
	
	/*
	pid4 = fork();
	
	if (pid4 == 0)
	{
		close(read);
		dup(p3_to_p4[read]);
		close(p3_to_p4[read]);
		close(p3_to_p4[write]);	
	
		execv(arguments4[0], arguments4);
	}
	
	
	
	*/
		close(p1_to_p2[read]);
		close(p1_to_p2[write]);  //close when pipe is done
	if (pipe2 > 0)
	{
		close(p2_to_p3[read]);
		close(p2_to_p3[write]);
	}
	
	//close (p3_to_p4[read]);
	//close (p3_to_p4[write]);
			
	waitpid(pid, &status, 0);
	waitpid(pid2, &status, 0);
	if (pipe2 > 0)
		waitpid(pid3, &status, 0);
//	waitpid(pid4, &status, 0);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void parseCommand(){   //tokenize the user input and check for ampersand
	char * inputDup = strdup(input);
	char * ptr;
	const char *const delim = " ";
	const char *const output = '>', input = '<', piping = '|';
	int count = 1;

//	printf("Parsing up command line\n");		//ERROR CHECKING
	ptr = strtok(inputDup , delim);
	icmd = ptr;						//setting input command without flags
	
	if (icmd != NULL)
	{
		arguments[0] = icmd;			//sets to user input possibly built-in
//		printf("%s\n", icmd);				//ERROR CHECKING
		
		while (ptr != NULL)
		{
			//printf("%s\n", ptr);				//ERROR CHECKING
			ptr = strtok(NULL, " ");	//tokenizing rest of input arguments
			arguments[count] = ptr;
			if (ptr != NULL)
			{
//				printf("Argument = %s\n", arguments[count]);	//ERROR CHECKING
//				puts("checking for outinpip");			//ERROR CHECKING
				if (strchr(arguments[count], output))
				{
//					printf("Argument = %s\n", arguments[count]);	//ERROR CHECKING
					Location = count;
					outDirec = count;
//					printf("Count = %i.\n", count);
				}
				else if (strchr(arguments[count], input))
				{
					Location = count;
					inDirec = count;
				}
				else if (strchr(arguments[count], piping))
				{
					pipeActive = pipeActive + 1;
					if (pipe1 == 0)
						pipe1 = count;
					else if (pipe2 == 0)
						pipe2 = count;
					else// if (pipe3 == 0)
						pipe3 = count;
				}
			}
			count++;
		}
		//printf("The last spot is %s\n", arguments[count - 2]);
			if (!strcmp(arguments[count - 2], "&"))
			{
//				puts("Removing &");			//ERROR CHECKING
				bkgndOn = 1;
				arguments[ count - 2] = NULL;
			}
			else
				bkgndOn = 0;
	}
//	else
//		puts("Null input");
	
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void readCommandLine(){   //Read user input command and save it
//  int characters;
//  int nchar = 100;
//  char *my_string;

	if (fgets(input, sizeConstant, stdin)){				//read in the line
		char* newline = strchr(input, '\n');			//set to search for return
		if (newline)
			*newline='\0';
	}
	
//    printf("~Final input is %s~\n", input); //error checking

    //free(text);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int verifyExistence(char* token, char *pathTemp, char ** args ){   //Verify if the command given exists

	//icmd = pathTemp;
    char * fullcmd = malloc(snprintf(NULL, 0, "%s/%s", token, pathTemp) + 1);  //dynamic
    sprintf(fullcmd, "%s/%s", token, pathTemp);  //safe print command

//    printf("Fullcmd = %s\n", fullcmd);		//ERROR CHECKING

    FILE *fp = fopen(fullcmd,"r");
    if( fp ) {
//      puts("Exists\n");					//ERROR CHECKING
		cmdStr = fullcmd;
		args[0] = fullcmd;			//set path location for command
//	printf("cmdStr = %s\n", cmdStr);	//ERROR CHECKING	
		return 1;
    }
    else {
//      puts("Doesn't Exists\n");		//ERROR CHECKING
		args[0] = NULL;
		cmdStr = NULL;
		return 0;
    }

}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int findPath(char *pathTemp, char ** args){    //takes in a command to search for

    char *path = strdup(getenv("PATH"));
    const char *const delim = ":";

    // For fully correct results, you must allocate the save_pointer
    // to be the length of the string you'll parse.
    char *save = (char *) malloc(strlen(path));   //allocating dynamic space

    // Here, we save the position of the save pointer so that we can free
    // it later.
    char *path_start_addr = path;
    char *save_start_addr = save;

    char *token = strtok_r(path, delim, &save);
    // Iterate through each token. Notice how save gets chopped each
    // round by token.
    for(; token != NULL; token = strtok_r(NULL, delim, &save)){
//    printf("save: %s\n", save);
//    printf("token: %s\n", token);
//      printf("\n");
      if (verifyExistence(token, pathTemp, args))
	  {
		free(save_start_addr);
		free(path_start_addr);
        return 1;  //verify if command exists at the path
	  }
    }

    //CREATE VARIABLE FOR WHEN COMMAND DNE AND DONT EXECUTE PREVIOUS COMMAND

//    printf("PATH: %s\n", getenv("PATH"));   //ERROR CHECKING

    // free all allocated memory
    free(save_start_addr);
    free(path_start_addr);
	return 0;

}


//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ MAIN FUNCTION $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(>^_^)>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main(/*int argc, char **argv*/){       // **argv == *xargv[]
	
	pid_t pid;// pid2, pid3,pid4;			//stores process ID
	int status;
	//getenv("PWD");
	strcpy(pwd,getenv("PWD"));		//initially set PWD
	
	while (1)
	{
		displayPrompt();		//display prompt to user for input
        readCommandLine();		//read in user input, check for ampersand
		parseCommand();         //tokenize the users input
		//printf("Input = %s", input);

		//check for no input
		if (!checkRedirection(Location))
			puts("Invalid command");
		else if (icmd == NULL)
			puts("Invalid command");
		else if (!strcmp(icmd, "exit"))	
		{
			puts("You have exited the shell!");	
			break;
			//puts("Not exiting");
		}
		//check for various key input's
		else if (input != NULL)
		{
			//Check if user command is a built in first
			if (!strcmp(icmd, "cd") || !strcmp(icmd, "echo") || !strcmp(icmd, "history")
				|| !strcmp(icmd, "which") || !strcmp(icmd, "viewproc"))
				findBuiltIns(icmd);
			

	//		printf("The comparison is: %i\n", strcmp(icmd,"cd"));
			
			//check if user input is not exit
			else if (strcmp(input, "exit"))
			{
				findPath(icmd, arguments);		//needs if check for built in or not
	//			printf("%s", icmd);
	//			puts("Parsing complete\n");		//ERROR CHECKING

				//Check for Piping
				
				startTime=findTime();
				puts(startTime);
				
				if(pipeActive > 0)
				{
					//if (
					linkPipes();
				}
				//Check for Redirection
				else if((pid = fork()) == 0)
				{
					if (outDirec > 0)
						outputDirec(outDirec);
					else if (inDirec > 0)
						inputDirec(inDirec);
					execv(cmdStr, arguments);  //cmdstr = /local/bin/ls argv = flags
				}
				//Check if background execution
				else
				{
					if (bkgndOn == 1)
						waitpid(pid, &status, WNOHANG);
					else if (bkgndOn == 0)
						waitpid(pid, &status, 0);
				}
				
				endTime=findTime();
				puts(endTime);
				
			}
			else
				puts("Command doesn't exist\n");
			addToHistory(); 
		}
		//RESET VARIABLES BEFORE RE-PROMPTING USER
//		printf("BKGND = %i Pipe = %i Input = %i Output = %i\n", bkgndOn, pipeActive,inDirec,outDirec);	//ERROR CHECKING
		bkgndOn = 0;
		pipeActive = 0;
		inDirec = outDirec = 0;
		Location = 0;
		pipe1 = pipe2 = pipe3 = 0;
//		printf("BKGND = %i Pipe = %i Input = %i Output = %i\n", bkgndOn, pipActive,inDirec,outDirec);	//ERROR CHECKING
    }	//end while
			
        return 0;
}
